/******************************************************************************************
文件名：main.c
作者：狼狼    版本：V1.0    创建日期：2021.9.3
文件描述：
    主程序。
历史修改记录：
1. 2021-9-3:V1.0 狼狼
创建。
2. 2022-5-11:V1.0 狼狼
代码公开。
******************************************************************************************/
//+------------------------------------------------------------------------------------------+
//|                                         头文件包含
//+------------------------------------------------------------------------------------------+
/*|*/#include <stdio.h>
/*|*/#include <string.h>
/*|*/#include <stdlib.h>
/*|*/#include <stdint.h>
/*|*/#include <stdbool.h>
/*|*/#include <signal.h>
/*|*/#include <unistd.h>
//|
/*|*/#include "connect_manager.h"
/*|*/#include "loice_master_parser.h"
/*|*/#include "project_debug.h"
//+------------------------------------------------------------------------------------------+
//|                                          全局变量
//+------------------------------------------------------------------------------------------+
/*|*/static bool program_run = true;
/*|*/static ST_CONNECT_MANAGER *pconnman_obj = NULL;
/*|*/static ST_PARSER *pparser_obj = NULL;
/*|*/static const uint8_t device_id_zero[28] = {0}; // 用作本设备ID
/*|*/#ifdef SAVE_STREAM_TO_FILE
/*|*/   FILE* pFile = NULL; // 测试：用于存储码流的文件句柄
/*|*/#endif
//+------------------------------------------------------------------------------------------+
//|  函数名称：recv_handle
//|  功能描述：接收回调函数
//|  参数说明：可供读取的Socket描述符，该描述符对应的会话ID
//|  返回值说明：成功返回CONN_SUCCESS，失败返回状态码（负值）
//|  备注：
//+------------------------------------------------------------------------------------------+
static int recv_handle(int conn_sock, uint32_t session_id)
{
    int ret = -1;
    
    /* 参数检查 */
    if(pparser_obj == NULL)
    {
        // 由于解析器不存在，因此返回会话不存在，以便使连接管理删除该连接
        OUTPUT("[RECV-WARNING]Non-existent parser object!\n");
        return PARSER_STAT_SESSION_NON_EXISTENT;
    }
    
    /* 调用解析器并对返回值做转换 */
    ret = pparser_obj->parser_function(pparser_obj, session_id, conn_sock);
    switch(ret)
    {
        case PARSER_SUCCESS:
            return CONN_SUCCESS;
        
        case PARSER_STAT_DISCONNECT_OR_CLOSE:
            return CONN_EXIT_STAT_DISCONNECT_OR_CLOSE;
        
        /* 需要重新审视设计的情况 */
        case PARSER_STAT_ILLEGAL_PARAM:
        case PARSER_STAT_PARSER_UNEXPECT_STATE:
        case PARSER_STAT_SESSION_UNEXPECT_STATE:
        case PARSER_STAT_UNSUPPORT_PROTOCOL:
            OUTPUT("[RECV-WARNING]parser_function return %#x!\n", ret);
            return CONN_SUCCESS;
        
        /* 以下返回值需要删除对应的连接 */
        case PARSER_EXIT_STAT_RX_ERROR:
            OUTPUT("[RECV-WARNING]Session[%d] recv failed!\n", session_id);
            return CONN_EXIT_STAT_RX_ERROR;
        case PARSER_STAT_SESSION_NON_EXISTENT:
            OUTPUT("[RECV-WARNING]Non-existent session[%d]!\n", session_id);
            return CONN_ERROR_SESSION_NON_EXISTENT;
        
        default:break;
    }
    
    return CONN_SUCCESS;
}

//+------------------------------------------------------------------------------------------+
//|  函数名称：send_data_reply_handle
//|  功能描述：接收来自解析器的消息回应回调，将数据转发到连接管理
//|  参数说明：会话ID，指向数据的指针，数据长度
//|  返回值说明：成功返回0，失败返回错误码（负值）
//|  备注：
//+------------------------------------------------------------------------------------------+
static int send_data_reply_handle(uint32_t session_id, const uint8_t *pdata, uint32_t data_len)
{
    if(pconnman_obj == NULL)return CONN_ERROR_SESSION_NON_EXISTENT;
    return pconnman_obj->send_package(pconnman_obj, session_id, pdata, data_len);
}

//+------------------------------------------------------------------------------------------+
//|  函数名称：send_data_reply_handle
//|  功能描述：处理某一会话KAP回应超时的事件
//|  参数说明：会话ID
//|  返回值说明：无
//|  备注：对于超时的会话，直接调用连接管理的接口关闭它。
//+------------------------------------------------------------------------------------------+
static void kap_timeout_handle(uint32_t session_id)
{
    pconnman_obj->pre_delete_connect(pconnman_obj, session_id);
}

//+------------------------------------------------------------------------------------------+
//|  函数名称：sig_handle
//|  功能描述：信号处理
//|  参数说明：信号值
//|  返回值说明：无
//|  备注：
//+------------------------------------------------------------------------------------------+
static void sig_handle(int signo)
{
	if(SIGINT == signo || SIGTERM == signo)
    {
        program_run = false;
    }
}

//+------------------------------------------------------------------------------------------+
//|  函数名称：main
//|  功能描述：主函数
//|  参数说明：默认
//|  返回值说明：无
//|  备注：当前仅用于测试。
//+------------------------------------------------------------------------------------------+
int main(int argc, const char *argv[])
{
    int ret = -1;
    uint32_t session_id = 0;
    
    /* 传参判断 */
    if(argc != 2)
    {
        printf("Usage:\n");
        printf("    ./filename DestIP\n");
        exit(0);
    }
    
    /* 初始化 */
	signal(SIGINT,  sig_handle); //注册Ctrl+C信号处理函数
	signal(SIGTERM, sig_handle); //注册无参数kill信号处理函数
    
    OUTPUT("[MAIN-INFO]Loice Master Demo Startup.\n");
    
    /* 创建连接管理和解析器实例 */
    connect_manager_create(&pconnman_obj, recv_handle);
    parser_create(&pparser_obj, PARSER_PROTOCOL_1, device_id_zero, send_data_reply_handle, kap_timeout_handle);
    
    /* 主循环 */
    int tmp;
    while(program_run)
    {
        printf("\n--------------------------------------------------\n");
        printf("Please choose your command:\n");
        printf("    1 Connect to [%s:1102]\n", argv[1]);
        printf("    2 Disconnect the link\n");
        printf("    3 Start H264-720P@30 video streaming\n");
        printf("    4 Stop video streaming\n");
        printf("    5 Exit demo\n");
        printf("--------------------------------------------------\n");
        
        scanf("%d", &tmp);
        switch(tmp)
        {
            case 1:
                if(session_id == 0)
                {
                    // ----------------------------------------
                    // 0. 创建TCP会话，连接到远程Loice服务器。
                    //    当前程序未设计重连机制，重连可在连接
                    //    管理中完成，并绑定新的套接字到会话ID。
                    // 1. 将TCP-Socket套接字添加到连接管理，获
                    //    取分配的会话ID
                    // 2. 根据会话ID将套接字添加到解析器，以初
                    //    始化对应的会话状态
                    // 3. 启动连接管理对该会话的接收
                    // ----------------------------------------
                    ret = tcp_client_create(argv[1], 1102);
                    if(ret < 0)
                    {
                        printf("[MAIN-ERROR]Create socket failed!\n");
                        perror("[Error Details]");
                        break;
                    }
                    pconnman_obj->add_connect(pconnman_obj, ret, &session_id);
                    pparser_obj->add_session(pparser_obj, session_id);
                    pconnman_obj->start_recv(pconnman_obj, session_id);
                    printf("[MAIN-INFO]TCP Connect done!\n");
#ifdef SAVE_STREAM_TO_FILE
                    pFile = fopen("loice_master_stream.h264", "wb");
                    if(!pFile)
                    {
                        OUTPUT("[FILE-ERROR]Open file failed!\n");
                        exit(0); // Only for test
                    }
#endif
                }
                else
                {
                    printf("[MAIN-INFO]Connection has already been established!\n");
                }
            break;
            case 2:
                if(session_id != 0)
                {
                    pconnman_obj->pre_delete_connect(pconnman_obj, session_id);
                    session_id = 0;
                    printf("[MAIN-INFO]TCP Connection will be disconnected!\n");
#ifdef SAVE_STREAM_TO_FILE
                    fclose(pFile);
#endif
                }
                else
                {
                    printf("[MAIN-INFO]Connection not yet established!\n");
                }
            break;
            case 3:
                if(session_id != 0)
                {
                    uint8_t *pmsg = NULL;
                    uint32_t msg_len = 0;
                    ST_INTERNAL_CMD internal_cmd = {0};
                    internal_cmd.session_id      = session_id;
                    internal_cmd.cmd_id          = CMD_ACTION;
                    internal_cmd.cmd_value       = 1;
                    internal_cmd.msg_seq         = 0;
                    internal_cmd.pargs           = NULL;
                    pparser_obj->cmd_trans(pparser_obj, &internal_cmd, 0, &pmsg, &msg_len, false);
                    pconnman_obj->send_package(pconnman_obj, session_id, pmsg, msg_len);
                    if(pmsg != NULL)free(pmsg);
                    printf("[MAIN-INFO]Send CMD[ID:%d - Value:%d]\n", internal_cmd.cmd_id, internal_cmd.cmd_value);
                }
                else
                {
                    printf("[MAIN-INFO]Connection not yet established!\n");
                }
            break;
            case 4:
                if(session_id != 0)
                {
                    uint8_t *pmsg = NULL;
                    uint32_t msg_len = 0;
                    ST_INTERNAL_CMD internal_cmd = {0};
                    internal_cmd.session_id      = session_id;
                    internal_cmd.cmd_id          = CMD_CONTROL;
                    internal_cmd.cmd_value       = 1;
                    internal_cmd.msg_seq         = 0;
                    internal_cmd.pargs           = NULL;
                    pparser_obj->cmd_trans(pparser_obj, &internal_cmd, 0, &pmsg, &msg_len, false);
                    pconnman_obj->send_package(pconnman_obj, session_id, pmsg, msg_len);
                    if(pmsg != NULL)free(pmsg);
                    printf("[MAIN-INFO]Send CMD[ID:%d - Value:%d]\n", internal_cmd.cmd_id, internal_cmd.cmd_value);
                }
                else
                {
                    printf("[MAIN-INFO]Connection not yet established!\n");
                }
            break;
            case 5:
                program_run = false;
            break;
            default:
                printf("[MAIN-INFO]Unknown choice!\n");
            break;
        }
    }
    
    /* 退出操作 */
    connect_manager_destroy(&pconnman_obj);
    parser_destroy(&pparser_obj);
    
#ifdef SAVE_STREAM_TO_FILE
    if(pFile)fclose(pFile);
#endif
    
    OUTPUT("[MAIN-INFO]Loice Master Demo Exit.\n");
    
	return 0;
}
